#ifndef __LIB_PROTOCOL_H_
#define __LIB_PROTOCOL_H_
#include "stdlib.h"
#include "stdbool.h"
/*
+----------------------------------------------------------------------------------------------------------+
|INDEX/ BIT|31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
+-----------------------------------------------------------------------------------------------------------------+
|    01    |                                       HEAD:0x5a5a5a5a                                         |      |
+----------------------------------------------------------------------------------------------------------+      +---+
|    02    |                                      PROTOCOL VERSION:0x1                                     |      |   |
+----------------------------------------------------------------------------------------------------------+      |   |
|    03    |                                    FRAME LENGTH(HEAD -> TAIL)                                 | HEAD |   |
+----------------------------------------+-----------------------------------------------------------------+      |   |
|    04    | CRC CONTROL:[31] 0:OFF,1:ON |                          RESERVED[30:0]                         | INFO |   |
+----------------------------------------+-----------------+-----------------------------------------------+      |   |
|    05    |   TOTAL FRAME COUNT:[31:16]                   |        FRAME INDEX[15:0]                      |      |CRC|
+----------------------------------------------------------+-----------------------------------------------+      |   |
|    06    |                    TOTAL DATA LENGTH(IF MULTIFRAME:ALL FRAM PAYLOAD)                          |      |   |
+----------------------------------+-----------------------+------------------------+-----------------------------+   |
|    07    |   ~FRAME SN[31:24]~   |      DUMMY[23:16]     |   PRODUCT CODE[15:8]   |   PRODUCT VER[7:0]   | PRO  |   |
+-----------------------------------------------------------------------------------+----------------------+ INFO |   |
|    08    |   ERROR CODE[31:24]   |     OPCODE1[23:16]    |                 OPCODE2[15:0]                 | CTL  |   |
+----------------------------------+-----------------------+------------------------------------------------------+   |
|   08+N   |                           DATA BLOCK(PAYLOAD1 ~ PAYLOADx N 4BYTE ALIGN)                       | DATA |   |
+-----------------------------------------------------------------------------------------------------------------+---+
|   08+N+1 |                            CRC32 CODE(CHECK ONLY WHEN CRC CONTROL=1)                          |
+----------------------------------------------------------------------------------------------------------+
|   08+N+2 |                                       TAIL:0xa5a5a5a5                                         |
+----------+-----------------------------------------------------------------------------------------------+
*/
#define PRO_DATA_ALIGN          4
#define PRO_MAGIC_HEAD          0x5A5A5A5A
#define PRO_VERSION_2           1
#define PRO_FLAG_CRC            0x00000000

#ifndef PRODUCT_TYPE_HOST
  #define PRODUCT_TYPE_HOST     0x1
#endif

#ifndef PRODUCT_VERSION_HOST
  #define PRODUCT_VERSION_HOST  0x1
#endif

#define SUCCEED                 0
#define ERR_OPT2                1 // invalid opcode 2
#define ERR_PAYLOAD_OPT         2 // invalid payload opcode
#define ERR_DATA                3 // invalid data or data length
#define ERR_EXEC                4 // excute error
#define ERR_CRC                 5 // crc32 check error


/* all elements in this structure is stored in network(big) endian */
typedef struct proto_head {
    unsigned int    magic;        // 01-HEAD              [31:0]
    unsigned int    version;      // 02-PROTOCOL VERSION  [31:0]
    unsigned int    frame_len;    // 03-FRAME LENGTH      [31:0]
    
    /* elements in one 4byte unit is arranged by little endian */   
    unsigned int flags;           // 04-CRC32 CONTROL     [31]

    unsigned short  frame_cur;    // 05-FRAME INDEX       [15:0]
    unsigned short  frame_sum;    //    TOTAL FRAME COUNT [31:16]

    unsigned int    payload_len;  // 06-TOTAL DATA LENGTH [31:0]

    unsigned char   product_ver;  // 07-PRODUCT VERSION   [7:0]
    unsigned char   product_type; //    PRODUCT CODE      [15:8]
    unsigned char   padsize;      //    DUMMY             [23:16]
    unsigned char   frame_seq;    //    FRAME SN(NO USE)  [31:24]

    unsigned short  opcode2;      // 08-OPCODE2           [15:0]
    unsigned char   opcode1;      //    OPCODE1           [23:16]
    unsigned char   errcode;      //    ERROR CODE        [31:24]
}ProHead;
#define PRO_HEAD_LEN            sizeof(ProHead)

#define PRO_MAGIC_TAIL          0xA5A5A5A5
typedef struct proto_tail {
    unsigned int    crc32;           // 08+N+1-CRC32 CODE    [31:0]
    unsigned int    magic;           // 08+N+2-TAIL          [31:0]
}ProTail;
#define PRO_TAIL_LEN            sizeof(ProTail)

#ifndef PRO_MAX_SIZE
#  define PRO_MAX_SIZE          (200)
#endif
#define PRO_MIN_SIZE            (PRO_HEAD_LEN + PRO_TAIL_LEN)
#define PAY_MAX_SIZE            (PRO_MAX_SIZE - PRO_MIN_SIZE)

typedef struct frame_list {
    struct frame_list *next;      // the next frame in this frame list
    int               len;        // frame data buffer length
    char              *data;      // frame data buffer including protocol head, payload, tail
}FrameList;

#define PRO_HEAD(f)     ((ProHead *)((f)->data))
#define PRO_TAIL(f)     ((ProTail *)((char *)(f)->data + (f)->len - PRO_TAIL_LEN))
#define PRO_PAYLOAD(f)  ((char *)(f)->data + PRO_HEAD_LEN)
#define PRO_PAYLEN(f)   ((f)->len - PRO_HEAD_LEN - PRO_TAIL_LEN - PRO_HEAD(f)->padsize)

/* @Brief: callback function for external frame parse (product specific OP code)
 * @Param: *chan: protocol channel pointer
 *         *payload: payload pointer of the first frame in the current frame list
 *         size: payload size of the first frame in the current frame list
 *         op1: opcode 1 of the first frame in the current frame list
 *         op2: opcode 2 of the first frame in the current frame list
 *         err: errcode  of the first frame in the current frame list
 *         *frames: the current frame list
 * @RET:   succ: 0
 *         fail: negative error number */
typedef int (* ProParseExt)(void *chan, char *payload, int size,
                           int op1, int op2, int err, 
                           struct frame_list *frames);

/* @Brief: callback function for read/write protocol frame data
 * @Param: *id : user specific identification pointer
 *         *buff: data buffer for read/write
 *         size: buffer size in BYTE
 * @RET:   succ: non-negative, bytes number actually readed/writed
 *         fail: negative error number */
typedef int (* ProRdWrFunc)(void *id, void *buff, int size);

typedef struct {
    pthread_mutex_t rlock;      // channel's read lock
    pthread_mutex_t wlock;      // channel's write lock
    pthread_mutex_t flock;      // channel's multiframe lock
    pthread_cond_t  fcond;      // channel's multiframe condition
    bool fack;                  // channel's multiframe ack flag
    int  fop1;                  // channel's multiframe op1 code
    int  fop2;                  // channel's multiframe op2 code
    int  ferr;                  // channel's multiframe err code
    int  fseq;                  // channel's multiframe sequence number
    FrameList *list_head;       // channel's last received frame list

    int chanid;                 // channel's identify, for tracking log info
    int prod_type;              // channel's product type
    int prod_ver;               // channel's product version
    int max_paylen;             // channel's maximum payload length, default PAY_MAX_SIZE

    void *id;                   // link id, will be passed into callback functions
    ProRdWrFunc read;           // callback function for reading protocol frame
    ProRdWrFunc write;          // callback function for writing protocol frame
    ProParseExt parse;          // callback function for parsing product specific opcode
    unsigned long recv_cnt;     // received frame count, including sub-frame and err-frame
    unsigned long send_cnt;     // sended frame count, including sub-frame
}ProChan;

/* @Brief: protocol channel initialize function
 * @Param: *chan: protocol channel pointer
 *         channel_id: protocol channel id, used to print log
 *         *id: channel id used by read/write, will be passed into the read/write callback function
 *         read:  callback function for frame read
 *         write: callback function for frame write
 *         parse: callback function for product relevant opcode paser
 *         prod_type: product type
 *         prod_ver:  product version
 * @RET:   succ: 0
 *         fail: negative error number */
int proChanInit(ProChan *chan, int channel_id,
                void *id, ProRdWrFunc read, ProRdWrFunc write, ProParseExt parse,
                int prod_type, int product_ver);

/* @Brief: protocol channel destroy function
 * @Param: *chan: protocol channel pointer
 * @RET:   succ: 0
 *         fail: negative error number */
int proChanDestroy(ProChan *chan);

/* @Brief: protocol frame receiving function
 * @Param: *chan: protocol channel pointer
 *         **list: frame list received from proFramesRecv
 * @RET:   succ: 0
 *         fail: negative error number */
int proFramesRecv(ProChan *chan, struct frame_list **list);

/* @Brief: protocol frame parsing function
 * @Param: *chan: protocol channel pointer
 *         **list: frame list received from proFramesRecv
 * @RET:   succ: 0
 *         fail: negative error number */
int proFramesParse(ProChan *chan, struct frame_list **list);

/* @Brief: protocol frame send function
 * @Param: *chan: protocol channel pointer
 *         *payload: payload buffer, pass NULL if no need to send payload
 *         size: payload size, pass 0 if no need to send payload
 *         op1: frame operation code 1
 *         op2: frame operation code 2
 *         err: frame error code
 * @RET:   succ: 0
 *         fail: negative error number */
int proFramesSend(ProChan *chan, char *payload, int size,
                   int op1, int op2, int err);

/* @Brief: protocol frame send function, no payload
 * @Param: *chan: protocol channel pointer
 *         op1: frame operation code 1
 *         op2: frame operation code 2
 *         err: frame error code
 * @RET:   succ: 0
 *         fail: negative error number */
static inline int proFramesSendEmpty(ProChan *chan, int op1, int op2, int err)
{
    return proFramesSend(chan, NULL, 0, op1, op2, err);
}

#endif/* __LIB_PROTOCOL_H_ */
